package vd

import (
	"fmt"
	"errors"
	"strings"
	"unicode"
	"reflect"
	"go/token"
	"math/big"
)


const EnumDummyInterfaceMethod = "Enum"

type GoStructOptions struct {
	Records     [] interface {}
	Validators  [] interface {}
	Adapters    [] interface {}
}

func GoTypeVdId(t reflect.Type) TypeId {
	var pkg = t.PkgPath()
	var name = t.Name()
	var project = (func() string {
		if strings.Contains(pkg, "/") {
			var segments = strings.Split(pkg, "/")
			var last = (len(segments) - 1)
			var project = segments[last]
			return project
		} else if pkg != "" {
			return pkg
		} else {
			return ""
		}
	})()
	return MakeTypeId("", project, name, "")
}

func GoFieldNameNormalize(name string) string {
	var chars = ([] rune)(name)
	var prev = rune(0)
	var buf strings.Builder
	for i, this := range chars {
		if i == 0 {
			if unicode.IsUpper(this) {
				buf.WriteRune(unicode.ToLower(this))
			} else {
				buf.WriteRune(this)
			}
		} else {
			if unicode.IsLower(prev) && unicode.IsUpper(this) {
				buf.WriteRune('-')
				buf.WriteRune(unicode.ToLower(this))
			} else {
				buf.WriteRune(this)
			}
		}
		prev = this
	}
	return buf.String()
}

func GoFieldNameDenormalize(name string) (string, bool) {
	var chars = ([] rune)(name)
	var prev = rune(0)
	var buf strings.Builder
	for i, this := range chars {
		if this == '-' {
			// do nothing
		} else {
			if i == 0 {
				if unicode.IsLower(this) {
					buf.WriteRune(unicode.ToUpper(this))
				} else {
					buf.WriteRune(this)
				}
			} else {
				if prev == '-' && unicode.IsLower(this) {
					buf.WriteRune(unicode.ToUpper(this))
				} else {
					buf.WriteRune(this)
				}
			}
		}
		prev = this
	}
	var raw = buf.String()
	if token.IsIdentifier(raw) {
		return raw, true
	} else {
		return "", false
	}
}

func CreateGoStructTransformer(opts GoStructOptions) (Transformer, string) {
	var types = make(map[TypeId] reflect.Type)
	var types_rev = make(map[reflect.Type] TypeId)
	var defaults = make(map[reflect.Type] reflect.Value)
	var register = func(t reflect.Type, expected reflect.Kind) {
		if t.Kind() != expected {
			switch expected {
			case reflect.Struct:    panic("struct expected")
			case reflect.Interface: panic("interface expected")
			default:
				panic("something went wrong")
		} }
		var _, registered = types_rev[t]
		if registered {
			return
		}
		var id = GoTypeVdId(t)
		var _, duplicate = types[id]
		if duplicate {
			panic("duplicate type: " + id.String())
		}
		types[id] = t
		types_rev[t] = id
	}
	for _, rec := range opts.Records {
		var struct_t = reflect.TypeOf(rec)
		defaults[struct_t] = reflect.ValueOf(rec)
		register(struct_t, reflect.Struct)
		var m, exists = struct_t.MethodByName(EnumDummyInterfaceMethod)
		if exists {
			if m.Type.NumIn() != 2 { panic("invalid enum dummy method") }
			var interface_t = m.Type.In(1)
			register(interface_t, reflect.Interface)
		}
	}
	var adapters = make(map[AdapterId] func(reflect.Value)(reflect.Value))
	for _, adapter := range opts.Adapters {
		var v = reflect.ValueOf(adapter)
		var t = v.Type()
		if v.Kind() == reflect.Func && t.NumIn() == 1 && t.NumOut() == 1 {
			var from = t.In(0)
			var to = t.Out(0)
			var from_id, from_exists = types_rev[from]
			if !(from_exists) { panic("bad adapter input: " + from.String()) }
			var to_id, to_exists = types_rev[to]
			if !(to_exists) { panic("bad adapter output: " + to.String()) }
			var pair = AdapterId {
				From: from_id,
				To:   to_id,
			}
			var _, duplicate = adapters[pair]
			if duplicate { panic("duplicate adapter: " + t.String()) }
			adapters[pair] = func(v reflect.Value) reflect.Value {
				var in = [] reflect.Value { v }
				var out = v.Call(in)[0]
				return out
			}
		} else {
			panic("invalid adapter type: " + t.String())
		}
	}
	var unit_t = reflect.TypeOf(struct{}{})
	var bool_t = reflect.TypeOf(false)
	var float_t = reflect.TypeOf(float64(0.0))
	var complex_t = reflect.TypeOf(complex128(0.0))
	var integer_t = reflect.TypeOf(big.NewInt(0))
	var string_t = reflect.TypeOf("")
	var binary_t = reflect.TypeOf(([] byte)(nil))
	var determine_type func(reflect.Type)(*Type)
	determine_type = func(t reflect.Type) *Type {
		switch t {
		case unit_t:
			return PrimitiveType(Unit)
		case bool_t:
			return PrimitiveType(Bool)
		case float_t:
			return PrimitiveType(Float)
		case complex_t:
			return PrimitiveType(Complex)
		case integer_t:
			return PrimitiveType(Integer)
		case string_t:
			return PrimitiveType(String)
		case binary_t:
			return PrimitiveType(Binary)
		default:
			if t.Kind() == reflect.Slice {
				var item = t.Elem()
				return ContainerType(Array, determine_type(item))
			} else if t.Kind() == reflect.Func && (
				t.NumIn() == 0 && t.NumOut() == 2 && t.Out(1) == bool_t) {
				var inner = t.Out(0)
				return ContainerType(Optional, determine_type(inner))
			} else if t.Kind() == reflect.Interface {
				var id, exists = types_rev[t]
				if !(exists) { panic("unregistered type: " + t.String()) }
				return AlgebraicType(Enum, id)
			} else if t.Kind() == reflect.Struct {
				var id, exists = types_rev[t]
				if !(exists) { panic("unregistered type: " + t.String()) }
				return AlgebraicType(Record, id)
			} else {
				panic("unsupported type: " + t.String())
			}
		}
	}
	var get_reflect_type func(*Type) (reflect.Type, error)
	get_reflect_type = func(t *Type) (reflect.Type, error) {
		switch t.kind {
		case Unit:    return unit_t, nil
		case Bool:    return bool_t, nil
		case Float:   return float_t, nil
		case Complex: return complex_t, nil
		case Integer: return integer_t, nil
		case String:  return string_t, nil
		case Binary:  return binary_t, nil
		case Array:
			var item_t, err = get_reflect_type(t.elementType)
			if err != nil { return nil, err }
			return reflect.SliceOf(item_t), nil
		case Optional:
			var inner_t, err = get_reflect_type(t.elementType)
			if err != nil { return nil, err }
			return reflect.FuncOf([] reflect.Type {},
				[] reflect.Type { inner_t, bool_t }, false), nil
		case Record:
			var rt, ok = types[t.identifier]
			if !ok { return nil,
				errors.New(fmt.Sprintf("unknown type %s", t.identifier)) }
			return rt, nil
		case Enum:
			var rt, ok = types[t.identifier]
			if !ok { return nil,
				errors.New(fmt.Sprintf("unknown type %s", t.identifier)) }
			return rt, nil
		default:
			panic("impossible branch")
		}
	}
	var V = func(u Object) reflect.Value {
		return u.(reflect.Value)
	}
	var I = func(u Object) Object {
		return V(u).Interface()
	}
	var serializer = Serializer {
		ReflectInward: func(obj Object) Object {
			return reflect.ValueOf(obj)
		},
		DetermineType: func(u Object) *Type {
			return determine_type(V(u).Type())
		},
		PrimitiveSerializer: PrimitiveSerializer {
			WriteBool:    func(u Object) bool { return I(u).(bool) },
			WriteFloat:   func(u Object) float64 { return I(u).(float64) },
			WriteComplex: func(u Object) complex128 { return I(u).(complex128) },
			WriteInteger: func(u Object) *big.Int { return I(u).(*big.Int) },
			WriteString:  func(u Object) string { return I(u).(string) },
			WriteBinary:  func(u Object) ([] byte) { return I(u).([] byte) },
		},
		ContainerSerializer: ContainerSerializer {
			IterateArray: func(u Object, f func(uint, Object) error) error {
				var v = V(u)
				for i := 0; i < v.Len(); i += 1 {
					var elem = v.Index(i)
					err := f(uint(i), elem)
					if err != nil { return err }
				}
				return nil
			},
			UnwrapMaybe: func(u Object) (Object, bool) {
				if u == nil {
					// zero value should NOT cause crash
					return nil, false
				}
				var v = V(u)
				var ret = v.Call([] reflect.Value {})
				var ok = ret[1].Interface().(bool)
				if ok {
					var inner = ret[0]
					return inner, true
				} else {
					return nil, false
				}
			},
		},
		AlgebraicSerializer: AlgebraicSerializer {
			IterateRecord: func(u Object, f func(string, Object) error) error {
				var v = V(u)
				for i := 0; i < v.NumField(); i += 1 {
					var field_info = v.Type().Field(i)
					var field_v = v.Field(i)
					var field_raw_name = field_info.Name
					var field_name = GoFieldNameNormalize(field_raw_name)
					err := f(field_name, field_v)
					if err != nil { return err }
				}
				return nil
			},
			Enum2Case: func(u Object) Object {
				return reflect.ValueOf(I(u))
			},
		},
	}
	var deserializer = Deserializer {
		ReflectOutward: func(u Object) Object {
			return I(u)
		},
		PrimitiveDeserializer: PrimitiveDeserializer {
			Unit:        reflect.ValueOf(struct{}{}),
			ReadBool:    func(obj bool) Object { return reflect.ValueOf(obj) },
			ReadFloat:   func(obj float64) Object { return reflect.ValueOf(obj) },
			ReadComplex: func(obj complex128) Object { return reflect.ValueOf(obj) },
			ReadInteger: func(obj *big.Int) (Object, bool) { return reflect.ValueOf(obj), true },
			ReadString:  func(obj string) Object { return reflect.ValueOf(obj) },
			ReadBinary:  func(obj ([] byte)) Object { return reflect.ValueOf(obj) },
		},
		ContainerDeserializer: ContainerDeserializer {
			CreateArray: func(array_t *Type) (Object, error) {
				var item_t, err = get_reflect_type(array_t.elementType)
				if err != nil { return nil, err }
				var slice_t = reflect.SliceOf(item_t)
				var slice_v = reflect.MakeSlice(slice_t, 0, 0)
				return slice_v, nil
			},
			AppendItem: func(array *Object, item Object) error {
				var array_v = (*array).(reflect.Value)
				var item_v = item.(reflect.Value)
				var appended_v = reflect.Append(array_v, item_v)
				*array = appended_v
				return nil
			},
			MaybeSome: func(inner Object, opt_t *Type) (Object, error) {
				var inner_v = inner.(reflect.Value)
				var opt_rt, err = get_reflect_type(opt_t)
				if err != nil { return nil, err }
				return reflect.MakeFunc(opt_rt, func(args ([] reflect.Value)) ([] reflect.Value) {
					return [] reflect.Value {
						inner_v,
						reflect.ValueOf(true),
					}
				}), nil
			},
			MaybeNone: func(opt_t *Type) (Object, error) {
				var opt_rt, err = get_reflect_type(opt_t)
				if err != nil { return nil, err }
				return reflect.MakeFunc(opt_rt, func(args ([] reflect.Value)) ([] reflect.Value) {
					return [] reflect.Value {
						reflect.Value {},
						reflect.ValueOf(false),
					}
				}), nil
			},
		},
		AlgebraicDeserializer: AlgebraicDeserializer {
			AssignObject: func(u Object, from *Type, to *Type) (Object, error) {
				var v = V(u)
				if TypeEqual(from, to) {
					return v, nil
				} else if from.Identifier() != (TypeId {}) &&
					to.Identifier() != (TypeId {}) {
					var pair = AdapterId {
						From: from.identifier,
						To:   to.identifier,
					}
					var adapter, exists = adapters[pair]
					if exists {
						return adapter(v), nil
					} else {
						return nil, errors.New("types are not compatible: " +
							fmt.Sprintf("\n\t%s\nis not adaptable to\n\t%s\n", from, to))
					}
				} else {
					return nil, errors.New("types are not compatible: " +
						fmt.Sprintf("\n\t%s\ndoes not equal to\n\t%s\n", from, to))
				}
			},
			CreateRecord: func(record_t TypeId, _ uint) (Object, func(string)(RecordField,bool), error) {
				var rt, exists = types[record_t]
				if !(exists) { return nil, nil, errors.New(fmt.Sprintf(
					"type %s does not exist", record_t)) }
				if rt.Kind() != reflect.Struct { return nil, nil, errors.New(fmt.Sprintf(
					"type %s is not a record type", record_t)) }
				var obj = reflect.New(rt).Elem()
				var default_, has_default = defaults[rt]
				if has_default {
					obj.Set(default_)
				}
				var fields = func(name string) (RecordField, bool) {
					var denormalized, ok = GoFieldNameDenormalize(name)
					if !(ok) { return RecordField {}, false }
					var field_info, exists = rt.FieldByName(denormalized)
					if !(exists) { return RecordField {}, false }
					return RecordField {
						Type:  determine_type(field_info.Type),
						Index: uint(field_info.Index[0]),
					}, true
				}
				return obj, fields, nil
			},
			FillField: func(record Object, index uint, value Object) {
				var record_v = V(record)
				var value_v = V(value)
				var field_v = record_v.Field(int(index))
				field_v.Set(value_v)
			},
			FinishRecord: func(record Object, _ TypeId) (Object, error) {
				return record, nil
			},
			Case2Enum: func(u Object, enum_id TypeId, case_id TypeId) (Object, error) {
				var enum_t, exists = types[enum_id]
				if !(exists) { return nil, errors.New(fmt.Sprintf(
					"type %s does not exist", enum_id)) }
				var v = V(u)
				if v.Type().ConvertibleTo(enum_t) {
					return v.Convert(enum_t), nil
				} else {
					return nil, errors.New(fmt.Sprintf(
						"%s is not a case type of the enum type %s",
						case_id, enum_id))
				}
			},
		},
	}
	var ts = Transformer {
		Serializer:   &serializer,
		Deserializer: &deserializer,
	}
	var src = generateCorrespondingSource(opts.Records, determine_type)
	return ts, src
}


